#include "matrix.h"
#include <stdio.h>
#include <stdarg.h>

/**
 * These macros are here to help you create tests more easily and keep them
 * clear.
 *
 * The basic idea with unit-testing is create a test function for every real
 * function and inside the test function declare some variables and execute the
 * function under test.
 *
 * Use the ASSERT_MACRO to verify correctness of values.
 */

/**
 * Evaluates b and continues if b is true.
 * If b is false, ends the test by returning false and prints a detailed
 * message about the failure.
 */
#define ASSERT_TRUE(b)                                                         \
do                                                                             \
{                                                                              \
	if( !(b) )                                                                 \
	{                                                                          \
		printf("\nAssertion failed at %s:%d %s\n",__FILE__,__LINE__,#b);  	   \
		return false;                                                          \
	}                                                                          \
} while (0)

/**
 * Macro used for running a test from the main function
 */
#define RUN_TEST(test)                                                         \
do                                                                             \
{                                                                              \
	printf("Running "#test"... ");                                             \
	if( test )                                                                 \
	{                                                                          \
		printf( "[OK]\n" );                                                    \
	}                                                                          \
} while(0)

Matrix initMatrix( int rows, int columns, ... )
{
	Matrix result = matrixCreate( rows, columns );
	if( result == NULL )
	{
		return NULL;
	}

	va_list vl;
	va_start( vl, columns );

	for( int r = 0; r < rows; ++r )
	{
		for( int c = 0; c < columns; ++c )
		{
			double real = va_arg(vl,double);
			double imaginary = va_arg(vl,double);
			matrixSet( result, r, c, complexCreate( real, imaginary ) );
		}
	}

	va_end( vl );

	return result;
}

#define ASSERT_VALID(c) ASSERT_TRUE( complexIsValid( (c) ) )
#define ASSERT_INVALID(c) ASSERT_TRUE( !complexIsValid( (c) ) )
#define ASSERT_NULL(b) ASSERT_TRUE( (b) == MATRIX_NULL_ARGUMENT )
#define ASSERT_SUCCESS(b) ASSERT_TRUE( (b) == MATRIX_SUCCESS )
#define ASSERT_MATRIX_EQUAL(A,B) ASSERT_TRUE( matrixIsEqual( (A), (B) ) )
#define ASSERT_MATRIX_NOT_EQUAL(A,B) ASSERT_TRUE( !matrixIsEqual( (A), (B) ) )

bool testGetSet()
{
	Complex c = complexCreate( 0, 0 );
	ASSERT_INVALID( matrixGet( NULL, 2, 3 ) );
	ASSERT_NULL( matrixSet( NULL, 7, -4, c ) );

	Matrix m = matrixCreate( 17, 29 );
	ASSERT_INVALID( matrixGet( m, 0, -20 ) );
	ASSERT_INVALID( matrixGet( m, 0, 29 ) );
	ASSERT_INVALID( matrixGet( m, 0, 129 ) );
	ASSERT_INVALID( matrixGet( m, -20, 0 ) );
	ASSERT_INVALID( matrixGet( m, 17, 0 ) );
	ASSERT_INVALID( matrixGet( m, 123, 0 ) );
	ASSERT_INVALID( matrixGet( m, -20, -20 ) );
	ASSERT_INVALID( matrixGet( m, 17, 29 ) );
	ASSERT_INVALID( matrixGet( m, 123, 129 ) );
	ASSERT_TRUE( matrixSet( m, 0, -20, c ) == MATRIX_OUT_OF_BOUNDS );
	ASSERT_TRUE( matrixSet( m, 0, 29, c ) == MATRIX_OUT_OF_BOUNDS );
	ASSERT_TRUE( matrixSet( m, 0, 129, c ) == MATRIX_OUT_OF_BOUNDS );
	ASSERT_TRUE( matrixSet( m, -20, 0, c ) == MATRIX_OUT_OF_BOUNDS );
	ASSERT_TRUE( matrixSet( m, 17, 0, c ) == MATRIX_OUT_OF_BOUNDS );
	ASSERT_TRUE( matrixSet( m, 123, 0, c ) == MATRIX_OUT_OF_BOUNDS );
	ASSERT_TRUE( matrixSet( m, -20, -20, c ) == MATRIX_OUT_OF_BOUNDS );
	ASSERT_TRUE( matrixSet( m, 17, 29, c ) == MATRIX_OUT_OF_BOUNDS );
	ASSERT_TRUE( matrixSet( m, 123, 129, c ) == MATRIX_OUT_OF_BOUNDS );

	int sign = 1;
	for( int row = 0; row < m->rows; ++row )
	{
		for( int column = 0; column < m->columns; ++column )
		{
			c = complexCreate( row * sign, column * -1 * sign );
			ASSERT_SUCCESS( matrixSet( m, row, column, c ) );
			ASSERT_TRUE( complexIsEqual( matrixGet(m,row,column), c ) );
			sign *= -1;
		}
	}

	matrixDestroy( m );

	return true;
}

bool testIsEqual()
{
	Matrix m1 = matrixCreate( 6, 2 );
	Matrix m2 = initMatrix( 3, 3,
			 13.5086,  39.2154,   8.3012,   8.2644,  16.4205,   4.3866,
			  5.8499,  12.6278,  -7.8783,  33.0417, -19.6003, -31.1269,
			  9.2588,  46.8545,   1.3925,  -3.0251,   0.0176,  13.0428);
	Matrix m3 = initMatrix( 3, 3,
			 13.5086,  39.2154,   8.3012,   8.2644,  16.4205,   4.3866,
			  5.8499,  12.6278,  -7.8783,  33.0417, -19.6003, -31.1269,
			  9.2588,  46.8545,   1.3925,  -3.0251,   0.0176,  13.0428);
	Matrix m4 = initMatrix( 3, 3,
			 13.5086,  39.2154,   8.3012,   8.2644,  16.4205,   4.3866,
			 -5.8499,  12.6278,  -7.8783,  33.0417, -19.6003, -31.1269,
			  9.2588,  46.8545,   1.3925,  -3.0251,   0.0176,  13.0428);

	ASSERT_MATRIX_EQUAL( NULL,NULL );
	ASSERT_MATRIX_NOT_EQUAL( m1,NULL );
	ASSERT_MATRIX_NOT_EQUAL( NULL,m1 );
	ASSERT_MATRIX_EQUAL( m1,m1 );
	ASSERT_MATRIX_NOT_EQUAL( m1,m2 );
	ASSERT_MATRIX_EQUAL( m2,m3 );
	ASSERT_MATRIX_EQUAL( m3,m2 );
	ASSERT_MATRIX_NOT_EQUAL( m3,m4 );
	ASSERT_MATRIX_NOT_EQUAL( m4,m3 );

	matrixDestroy( m1 );
	matrixDestroy( m2 );
	matrixDestroy( m3 );
	matrixDestroy( m4 );
	return true;
}
/*testing the matrixCreate funcion possible return values*/
bool testCreate()
{
	Matrix matrix1 = matrixCreate(2, 2);
	Matrix matrixZero = initMatrix( 2, 2,
			   0.0, 0.0, 0.0, 0.0,
			   0.0, 0.0, 0.0, 0.0);
	Matrix matrixNotZero = initMatrix( 2, 2,
			   0.0, 0.0, 0.0, 0.0,
			   0.0, 0.0, 0.0, 1.0);

	ASSERT_MATRIX_EQUAL( matrix1, matrixZero );
	ASSERT_MATRIX_NOT_EQUAL( matrix1, matrixNotZero );
	ASSERT_TRUE( matrixCreate( 3, -5 ) == NULL );
	ASSERT_TRUE( matrixCreate( -1, 8 ) == NULL );
	ASSERT_TRUE( matrixCreate( 0,2 ) == NULL );
	ASSERT_TRUE( matrixCreate( 6,0 ) == NULL );

	matrixDestroy( matrix1 );
	matrixDestroy( matrixZero);
	matrixDestroy( matrixNotZero);
	return true;
}

bool testDestroy()
{
/*Tested via valgrind*/
	return true;
}

/*testing the matrixCreateUnit function*/
bool testCreateUnit()
{
	Matrix unitSize3 = matrixCreateUnit(3);
	Matrix m1 = initMatrix( 3, 3,
			1.0, 0.0, 0.0, 0.0, 0.0, 0.0,
			0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
			0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
	Matrix matrixNotUnit = initMatrix( 3, 3,
			1.0, 0.0, 0.0, 0.0, 0.0, 0.0,
			0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
			0.0, 0.0, 0.0, 0.0, 1.0, -4.0);

	ASSERT_TRUE( matrixCreateUnit(0) == NULL );
	ASSERT_TRUE( matrixCreateUnit(-10) == NULL );
	ASSERT_MATRIX_EQUAL( unitSize3, m1 );
	ASSERT_MATRIX_NOT_EQUAL( unitSize3, matrixNotUnit );

	matrixDestroy( unitSize3 );
	matrixDestroy( m1 );
	matrixDestroy( matrixNotUnit );
	return true;
}

/*testing the matrixAdd function*/
bool testAdd()
{
	Matrix matrix1 = initMatrix( 2, 3,
			-13.0,   39.0,  8.03,   8.0,  16.0,  14.0,
			  9.0,   46.0,   2.0,  -3.0,   0.0,  13.0);
	Matrix matrix2 = initMatrix( 2, 3,
			 13.5,  -39.0,   8.5,   8.0,   16.0,   4.59,
			-5.32,   12.0,  -7.5,  33.0,  -19.0, -31.26);
	Matrix matrix3 = matrixCreate( 2, 6 );
	Matrix matrix4 = matrixCreate( 4, 3 );
	Matrix result = initMatrix( 2, 3,
			  0.5,    0.0,   16.53,   16.0,   32.0,   18.59,
			 3.68,   58.0,    -5.5,   30.0,  -19.0,  -18.26);

	ASSERT_TRUE( matrixAdd(matrix1, matrix2) == MATRIX_SUCCESS );
	ASSERT_MATRIX_EQUAL( result, matrix1 );
	ASSERT_TRUE( matrixAdd(matrix2, matrix3) == MATRIX_BAD_DIMENSIONS );
	ASSERT_TRUE( matrixAdd(matrix4, matrix1) == MATRIX_BAD_DIMENSIONS );
	ASSERT_NULL( matrixAdd(NULL, matrix1) );
	ASSERT_NULL( matrixAdd(matrix3, NULL) );

	matrixDestroy(matrix1);
	matrixDestroy(matrix2);
	matrixDestroy(matrix3);
	matrixDestroy(matrix4);
	matrixDestroy(result);
	return true;
}
/*testing matrixSubtract function*/
bool testSubtract()
{
	Matrix matrix1 = initMatrix( 2, 3,
			-10.0,   25.0,   3.0,    5.0,   8.0,  14.0,
			  9.0,   40.0,   2.0,   -3.0,   0.0,  13.0);
	Matrix matrix2 = initMatrix( 2, 3,
			 13.5,  -39.0,   6.0,    8.0,  16.0,   4.5,
			 -5.0,   12.0,	-7.5,  33.54, -19.0, -31.0);
	Matrix matrix3 = matrixCreate( 2, 4);
	Matrix matrix4 = matrixCreate( 8, 3);
	Matrix result = initMatrix( 2, 3,
			-23.5,   64.0,  -3.0,   -3.0,   -8.0,    9.5,
			 14.0,   28.0,   9.5, -36.54,	19.0,   44.0);

	ASSERT_NULL( matrixSubtract(NULL, matrix2));
	ASSERT_NULL( matrixSubtract(matrix1, NULL));

	ASSERT_TRUE( matrixSubtract(matrix1, matrix2) == MATRIX_SUCCESS );
	ASSERT_MATRIX_EQUAL( result, matrix1 );
	ASSERT_TRUE( matrixSubtract(matrix2, matrix3) == MATRIX_BAD_DIMENSIONS );
	ASSERT_TRUE( matrixSubtract(matrix4, matrix1) == MATRIX_BAD_DIMENSIONS );

	matrixDestroy(matrix1);
	matrixDestroy(matrix2);
	matrixDestroy(matrix3);
	matrixDestroy(matrix4);
	matrixDestroy(result);
	return true;
}

/*testing the matrixNegate function*/
bool testNegate()
{
	Matrix matrix1 = initMatrix( 2, 3,
			-10.34,     25.2,  0.3331,     5.76,   8.77,    14.2,
		   9.56456, 40.34563,   -2.345,  -3.874,  0.848,  13.846);
	Matrix result = initMatrix( 2, 3,
			  10.34,     -25.2,  -0.3331,   -5.76,   -8.77,    -14.2,
		   -9.56456, -40.34563,    2.345,   3.874,  -0.848,  -13.846);

	ASSERT_TRUE( matrixNegate(matrix1 ) == MATRIX_SUCCESS);
	ASSERT_MATRIX_EQUAL( result, matrix1 );
	ASSERT_NULL( matrixNegate(NULL) );

	matrixDestroy(matrix1);
	matrixDestroy(result);
	return true;
}

/*testing the matrixMultiply function*/
bool testMultiply()
{
	Matrix result = NULL;
	Matrix matrix1 = matrixCreate( 1, 7);
	Matrix matrix2 = initMatrix( 2, 3,
			12.1, -1.0, 3.0, 1.5, 1.6, 0.0,
			14.0, -1.3, 1.0, 0.5, 2.6, 0.0);
	Matrix matrix3 = matrixCreateUnit(3);

	ASSERT_NULL(matrixMultiply( NULL, matrix1, &result));
	ASSERT_NULL(matrixMultiply( matrix2, NULL, &result));
	ASSERT_NULL(matrixMultiply( matrix1, matrix2, NULL));

	ASSERT_TRUE(matrixMultiply( matrix1, matrix2, &result ) ==
			MATRIX_BAD_DIMENSIONS);
	ASSERT_SUCCESS(matrixMultiply(matrix2, matrix3, &result));
	ASSERT_MATRIX_EQUAL(result, matrix2);

	matrixDestroy(matrix1);
	matrixDestroy(matrix2);
	matrixDestroy(matrix3);
	matrixDestroy(result);
	return true;
}

/*testing the matrixSwapRows function*/
bool testSwapRows()
{
	Matrix matrix1 = initMatrix( 4, 3,
			 13.5086,  39.2154,   8.3012,   8.2644,  16.4205,   4.3866,
			  5.8499,  12.6278,  -7.8783,  33.0417, -19.6003, -31.1269,
			  9.2588,  46.8545,   1.3925,  -3.0251,   0.0176,  13.0428,
			  5.4234,  353.345,   -344.3,  -22.434,  24.5552,  222.345);
	Matrix result1 = initMatrix( 4, 3,
			 13.5086,  39.2154,   8.3012,   8.2644,  16.4205,   4.3866,
			  9.2588,  46.8545,   1.3925,  -3.0251,   0.0176,  13.0428,
			  5.8499,  12.6278,  -7.8783,  33.0417, -19.6003, -31.1269,
			  5.4234,  353.345,   -344.3,  -22.434,  24.5552,  222.345);

	ASSERT_SUCCESS( matrixSwapRows(matrix1, 1, 2) );
	ASSERT_MATRIX_EQUAL( matrix1, result1 );
	ASSERT_SUCCESS( matrixSwapRows(matrix1, 0, 2));
	ASSERT_SUCCESS( matrixSwapRows(result1, 0, 2));
	ASSERT_MATRIX_EQUAL( result1, matrix1 );
	ASSERT_NULL( matrixSwapRows(NULL, 2, 5) );
	ASSERT_TRUE( matrixSwapRows(matrix1, 0, 4) == MATRIX_OUT_OF_BOUNDS);
	ASSERT_TRUE( matrixSwapRows(result1, 2, -1) == MATRIX_OUT_OF_BOUNDS);
	ASSERT_TRUE( matrixSwapRows(matrix1, 2, 7) == MATRIX_OUT_OF_BOUNDS);

	matrixDestroy(matrix1);
	matrixDestroy(result1);
	return true;
}
/*testing the matrixMultiplyRowByScalar function*/
bool testMultiplyRowByScalar()
{
	Matrix matrix1 = initMatrix( 3, 3,
			 13.5,   2.34,   2.44,   2.04,  22.0,  3.55,
			  1.2,   -4.5,  -6.43,  -3.33,  1.00,  -5.6,
			  0.0,   -7.4,   5.22,   0.44,  2.02,  2.66);
	Complex scalar = complexCreate(2.4, -1.2);
	Matrix result1 = initMatrix( 3, 3,
			 13.5,   2.34,   2.44,     2.04,  22.0,   3.55,
			-2.52, -12.24,  -19.428, -0.276, -4.32, -14.64,
			  0.0,   -7.4,   5.22,     0.44,  2.02,   2.66);

	ASSERT_SUCCESS( matrixMultiplyRowByScalar(matrix1, 1, scalar) );
	ASSERT_MATRIX_EQUAL( matrix1, result1 );
	ASSERT_NULL( matrixMultiplyRowByScalar(NULL, 1, scalar) );
	ASSERT_NULL( matrixMultiplyRowByScalar(NULL, -3, scalar) );
	ASSERT_TRUE( matrixMultiplyRowByScalar(matrix1, -1, scalar) ==
			MATRIX_OUT_OF_BOUNDS);
	ASSERT_TRUE( matrixMultiplyRowByScalar(matrix1, 7, scalar) ==
			MATRIX_OUT_OF_BOUNDS);


	matrixDestroy(matrix1);
	matrixDestroy(result1);
	return true;
}

/*testing the matrixSubtractRow function*/
bool testSubtractRow()
{
	Matrix matrix1 = initMatrix( 2, 3,
			1.3, 2.6,  5.0, -3.4, 4.2, -9.1,
			4.4, 8.3, -2.5, -3.4, 1.0,  0.3);
	Complex scalar = complexCreate(2.4, 5.3);
	Matrix result = initMatrix( 2, 3,
			34.73, -40.64, -7.02, 18.01, 3.39, -15.12,
			  4.4,    8.3,  -2.5,  -3.4,  1.0,    0.3);

	ASSERT_NULL(matrixSubtractRow(NULL, 2, 3, scalar));

	ASSERT_TRUE(matrixSubtractRow(matrix1, -1, 1, scalar) ==
				MATRIX_OUT_OF_BOUNDS);
	ASSERT_TRUE(matrixSubtractRow(matrix1, 1, -4, scalar) ==
				MATRIX_OUT_OF_BOUNDS);
	ASSERT_TRUE(matrixSubtractRow(matrix1, -1, 7, scalar) ==
				MATRIX_OUT_OF_BOUNDS);
	ASSERT_TRUE(matrixSubtractRow(matrix1, 1, 0, complexCreate(0, 0)) ==
			    MATRIX_SUCCESS);
	ASSERT_TRUE(matrixSubtractRow(matrix1, 0, 1, scalar) == MATRIX_SUCCESS);
	ASSERT_MATRIX_EQUAL(matrix1, result);

	matrixDestroy(matrix1);
	matrixDestroy(result);
	return true;
}

/*testing the matrixInverse function*/
bool testInverse()
{
	Matrix matrix1 = initMatrix( 3, 3,
			3.0, 2.5, 7.0, 1.0, 2.3, 1.1,
			7.0, 3.0, 1.0, 4.7, 1.0, 2.2,
			1.0, 1.1, 1.0, 1.1, 1.0, 1.1);
	Matrix matrix2 = matrixCreate( 1, 7);
	Matrix matrix3 = matrixCreate( 7, 1);
	Matrix matrix4 = initMatrix( 2, 2,
			1.4, -3.1, 4.7, 5.8,
			1.4, -3.1, 4.7, 5.8);
	Matrix unitMatrix = matrixCreateUnit(3);
	Matrix result, result1, result2;

	ASSERT_NULL(matrixInverse(NULL, &result));
	ASSERT_NULL(matrixInverse(matrix1, NULL));
	ASSERT_NULL(matrixInverse(NULL, NULL));

	ASSERT_TRUE(matrixInverse(matrix2, &result) ==
				MATRIX_BAD_DIMENSIONS);
	ASSERT_TRUE(matrixInverse(matrix3, &result) ==
				MATRIX_BAD_DIMENSIONS);
	ASSERT_TRUE(matrixInverse(matrix4, &result) ==
				 MATRIX_SINGULAR);
	ASSERT_SUCCESS(matrixInverse(matrix1, &result));
	ASSERT_SUCCESS(matrixMultiply(result, matrix1, &result1));
	ASSERT_MATRIX_EQUAL(unitMatrix, result1);
	ASSERT_SUCCESS(matrixInverse(unitMatrix, &result2));
	ASSERT_MATRIX_EQUAL(result2, unitMatrix);

	matrixDestroy(matrix1);
	matrixDestroy(matrix2);
	matrixDestroy(matrix3);
	matrixDestroy(matrix4);
	matrixDestroy(unitMatrix);
	matrixDestroy(result);
	matrixDestroy(result1);
	matrixDestroy(result2);

	return true;
}

/*testing the matrixSolveEquationSystem function*/
bool testSolveEquationSystem()
{
	Matrix matrix1 = initMatrix( 3, 3,
			1.3,  2.6,  3.2,   5.5,  12.7,   1.1,
			6.3,  4.2, -1.0,  -3.0,  -9.0, -11.4,
		   -6.6, -4.3,  2.6,  -5.7,   0.4,   3.2);
	Matrix matrix2 = initMatrix( 3, 1,
			2.5,  4.3,
		   -5.2, -2.2,
			8.2,  32.6);
	Matrix solve, solve1;
	Matrix matrix3 = matrixCreate( 2, 3);
	Matrix matrix4 = matrixCreate( 3, 5);
	Matrix matrix5 = matrixCreate( 5, 1);
	Matrix matrix6 = initMatrix( 3, 3,
			2.0,  5.0, 3.1,  2.0, 8.0, 16.5,
			2.0,  5.0, 3.1,  2.0, 8.0, 16.5,
			9.3, 47.0, 1.0, -4.0, 0.0,  0.0);

	ASSERT_NULL(matrixSolveEquationSystem(matrix1, matrix2, NULL));
	ASSERT_NULL(matrixSolveEquationSystem(matrix1, NULL, &solve));
	ASSERT_NULL(matrixSolveEquationSystem(NULL, matrix2, &solve));
	ASSERT_NULL(matrixSolveEquationSystem(matrix1, NULL, NULL));
	ASSERT_NULL(matrixSolveEquationSystem(NULL, matrix2, NULL));
	ASSERT_NULL(matrixSolveEquationSystem(NULL, NULL, &solve));
	ASSERT_NULL(matrixSolveEquationSystem(NULL, NULL, NULL));

	ASSERT_TRUE((matrixSolveEquationSystem(matrix3, matrix1, &solve)) ==
				MATRIX_BAD_DIMENSIONS);
	ASSERT_TRUE((matrixSolveEquationSystem(matrix1, matrix4, &solve)) ==
				MATRIX_BAD_DIMENSIONS);
	ASSERT_TRUE((matrixSolveEquationSystem(matrix1, matrix5, &solve)) ==
				MATRIX_BAD_DIMENSIONS);
	ASSERT_TRUE((matrixSolveEquationSystem(matrix6, matrix2, &solve)) ==
				MATRIX_SINGULAR);
	ASSERT_SUCCESS(matrixSolveEquationSystem(matrix1, matrix2, &solve));
	matrixMultiply(matrix1, solve, &solve1);;
	ASSERT_MATRIX_EQUAL(solve1, matrix2);

	matrixDestroy(matrix1);
	matrixDestroy(matrix2);
	matrixDestroy(matrix3);
	matrixDestroy(matrix4);
	matrixDestroy(matrix5);
	matrixDestroy(matrix6);
	matrixDestroy(solve);
	matrixDestroy(solve1);
	return true;
}

int main()
{
	RUN_TEST( testGetSet() );
	RUN_TEST( testIsEqual() );
	RUN_TEST( testCreate() );
	RUN_TEST( testDestroy() );
	RUN_TEST( testCreateUnit() );
	RUN_TEST( testAdd() );
	RUN_TEST( testSubtract() );
	RUN_TEST( testNegate() );
	RUN_TEST( testMultiply() );
	RUN_TEST( testSwapRows() );
	RUN_TEST( testMultiplyRowByScalar() );
	RUN_TEST( testSubtractRow() );
	RUN_TEST( testInverse() );
	RUN_TEST( testSolveEquationSystem() );

	return 0;
}

